Skip to content

fix(startup): prevent FOUC flash between window.show() and CSS load#30

Open
pythoninthegrass wants to merge 5 commits intomainfrom
pythoninthegrass/task-298
Open

fix(startup): prevent FOUC flash between window.show() and CSS load#30
pythoninthegrass wants to merge 5 commits intomainfrom
pythoninthegrass/task-298

Conversation

@pythoninthegrass
Copy link
Collaborator

Summary

Fix a flash of unstyled content (FOUC) regression introduced by early window.show() in commit 2f35425. The fix uses three complementary approaches: critical inline theme background colors, explicit backgroundColor in JavaScript, and requestAnimationFrame instead of setTimeout for the final reveal.

Changes

  • index.html: Moved inline <style> before scripts and added hardcoded theme background colors for light (#ffffff), dark (#09090b), metro-teal (#1e1e1e), and neon-love (#1f1731) to cover the gap before Tailwind CSS loads.
  • main.js: Enhanced applyInitialTheme() to set inline style.backgroundColor on <html> and handle browser-mode fallback. Replaced setTimeout(0) with requestAnimationFrame() for the final reveal.
  • tests: Added two new regression tests for inline background colors and updated stale comments. All 11 FOUC tests pass; full suite: 692 passed.

Testing

  • 11/11 startup FOUC prevention tests pass
  • 692/694 total tests pass (2 pre-existing skips)
  • Manual verification recommended for all themes on real Tauri app

🤖 Generated with Claude Code

pythoninthegrass and others added 5 commits March 11, 2026 23:54
The early window.show() introduced in commit 2f35425 created a gap where the
Tauri window is visible but the body is hidden via x-cloak. During this gap,
the <html> element has no explicit background color, causing a white flash
when the user sees the webview/document background instead of the themed one.

Fix with three changes:
1. Add critical theme background colors to inline <style> for light, dark,
   metro-teal, and neon-love variants. These hardcoded hex values cover the
   gap before Tailwind CSS bundle loads.
2. Set inline style.backgroundColor on <html> in applyInitialTheme() alongside
   theme classes. This ensures the background is correct even if external CSS
   hasn't been computed yet. Also handle the fallback case when settings
   aren't initialized (browser mode).
3. Replace setTimeout(0) with requestAnimationFrame() for the final reveal.
   The window is already visible at this point, so rAF fires reliably and
   guarantees styles are computed before paint.

All 11 FOUC regression tests pass. Full test suite: 692 passed (4.0m).

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
The previous CSS-only fix reduced FOUC but a brief flash remained because
the native window/webview background was still the default white when
window.show() was called — before the webview had rendered the HTML with
inline theme styles.

Fix by setting the native background color at three levels:
1. Static backgroundColor in tauri.conf.json as initial default (#ffffff)
2. WebviewWindow.setBackgroundColor() called with the resolved theme color
   BEFORE window.show(), so the first visible frame matches the theme
3. Added core:window:allow-set-background-color and
   core:webview:allow-set-webview-background-color permissions

Also refactored applyInitialTheme() to return the resolved hex color and
extracted themeBackgrounds to module scope for reuse.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
If setBackgroundColor() fails (e.g. missing capability permission), the
window.show() call was also skipped because both were in the same
try/catch block. Split them so window.show() always executes regardless
of whether the background color API succeeds.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The early window.show() (added in 2f35425 for WebKit IPC throttling)
was creating a 600ms white flash on dark themes. The window appeared
before CSS/Alpine had loaded, exposing the default white webview
background.

Fix by keeping the window hidden (visible: false) throughout the entire
initialization sequence. revealApp() now atomically sets the native
window/webview background color, calls window.show(), and removes
x-cloak so the first visible frame is fully styled.

The IPC throttling tradeoff is acceptable: settings.init() already ran
while hidden, and the remaining IPC calls add minimal overhead compared
to 600ms of visible white flash.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The destructuring of window.__TAURI__.window and getCurrentWindow() can
throw when Tauri APIs are partially mocked (e.g. in E2E tests with
mocked Tauri context). The error was outside any try/catch block, which
prevented document.body.removeAttribute('x-cloak') from executing,
leaving the UI permanently hidden.

Wrap the entire Tauri block in a top-level try/catch so the x-cloak
removal always executes regardless of Tauri API availability.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant